package com.renfei.multimediatask.ui.task8;

import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.util.Log;
import android.view.Surface;

import androidx.annotation.RequiresApi;

import com.renfei.multimediatask.MainActivity;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;

public class Mp4MoviePlayer extends Thread {
    private static final String TAG = Mp4MoviePlayer.class.getSimpleName();
    private static final boolean VERBOSE = true;
    private volatile boolean mIsStopRequested;

    private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();

    private File mSourceFile;
    private Surface mOutputSurface;
    private int mVideoWidth;
    private int mVideoHeight;

    private int frameRate;

    public Mp4MoviePlayer(File mSourceFile, Surface mOutputSurface) {
        this.mSourceFile = mSourceFile;
        this.mOutputSurface = mOutputSurface;

        getVideoSize();
    }

    private void getVideoSize() {
        MediaExtractor extractor = null;
        try {

            extractor = new MediaExtractor();
            extractor.setDataSource(mSourceFile.toString());

            int trackIndex = MediaUtil.selectTrack(extractor);
            if (trackIndex < 0) {
                throw new RuntimeException("No video track found in " + mSourceFile);
            }

            extractor.selectTrack(trackIndex);

            MediaFormat mediaFormat = extractor.getTrackFormat(trackIndex);

            mVideoWidth = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
            mVideoHeight = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
            frameRate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);

            if (VERBOSE) {
                Log.d(TAG, "Video size is " + mVideoWidth + "x" + mVideoHeight);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (extractor != null) {
                extractor.release();
            }
        }
    }


    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void run() {
        super.run();

        MediaExtractor extractor = null;
        MediaCodec decoder = null;

        try {
            if (!mSourceFile.canRead()) {
                throw new FileNotFoundException("Unable to read " + mSourceFile);
            }

            extractor = new MediaExtractor();
            extractor.setDataSource(mSourceFile.toString());

            int trackIndex = MediaUtil.selectTrack(extractor);
            if (trackIndex < 0) {
                throw new RuntimeException("No video track found in " + mSourceFile);
            }
            extractor.selectTrack(trackIndex);
            MediaFormat format = extractor.getTrackFormat(trackIndex);
            String mime = format.getString(MediaFormat.KEY_MIME);
            decoder = MediaCodec.createDecoderByType(mime);

            decoder.configure(format, mOutputSurface, null, 0);
            decoder.start();


            final int TIMEOUT_USEC = 10000;
            int inputChunk = 0;
            long firstInputTimeNsec = -1;

            boolean outputDone = false;
            boolean inputDone = false;


            while (!outputDone) {
                if (VERBOSE) Log.d(TAG, "loop");
                if (mIsStopRequested) {
                    Log.d(TAG, "Stop requested");
                    return;
                }

                if (!inputDone) {

                    int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);

                    if (inputBufIndex >= 0) {
                        if (firstInputTimeNsec == -1) {
                            firstInputTimeNsec = System.nanoTime();
                        }
                        ByteBuffer inputBuffer = decoder.getInputBuffer(inputBufIndex);

                        int chunkSize = extractor.readSampleData(inputBuffer, 0);
                        if (chunkSize < 0) {
                            decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            inputDone = true;
                            if (VERBOSE) Log.d(TAG, "sent input EOS");
                        } else {
                            if (extractor.getSampleTrackIndex() != trackIndex) {
                                Log.w(TAG, "WEIRD: got sample from track " + extractor.getSampleTrackIndex() + ", expected " + trackIndex);
                            }
                            long presentationTimeUs = extractor.getSampleTime();
                            decoder.queueInputBuffer(inputBufIndex, 0, chunkSize, presentationTimeUs, 0 /*flags*/);
                            if (VERBOSE) {
                                Log.d(TAG, "submitted frame " + inputChunk + " to dec, size=" + chunkSize);
                            }
                            inputChunk++;
                            extractor.advance();
                        }
                    } else {
                        if (VERBOSE) Log.d(TAG, "input buffer not available");
                    }

                }

                if (!outputDone) {
                    int outputBufferIndex = decoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);

                    if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {//队列请求超时
                        if (VERBOSE) Log.d(TAG, "no output from decoder available");
                    } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {//输出缓冲区改变
                        if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
                    } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {//输出格式改变
                        MediaFormat newFormat = decoder.getOutputFormat();
                        if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
                    } else if (outputBufferIndex < 0) {//出现其他异常
                        throw new RuntimeException("unexpected result from decoder.dequeueOutputBuffer: " + outputBufferIndex);
                    } else {

                        if (firstInputTimeNsec != 0) {
                            long nowNsec = System.nanoTime();
                            Log.d(TAG, "startup lag " + ((nowNsec - firstInputTimeNsec) / 1000000.0) + " ms");
                            firstInputTimeNsec = 0;
                        }

                        if (VERBOSE)
                            Log.d(TAG, "surface decoder given buffer " + outputBufferIndex + " (size=" + mBufferInfo.size + ")");

                        if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                            if (VERBOSE) Log.d(TAG, "output EOS");
                            outputDone = true;
                        }
                        boolean doRender = (mBufferInfo.size != 0);

                        sleep(1000/frameRate);
                        decoder.releaseOutputBuffer(outputBufferIndex, doRender);
                    }
                }
            }


        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (decoder != null) {
                decoder.stop();
                decoder.release();
                decoder = null;
            }
            if (extractor != null) {
                extractor.release();
                extractor = null;
            }
        }
    }


    public void requestStop() {
        mIsStopRequested = true;
    }

}
